#include "SimulationPCAL6416A.h"

std::unique_ptr<SimulationPCAL6416A> SimulationPCAL6416A::instance_uptr = nullptr;

void SimulationPCAL6416A::Init(bool device_id,
                               const std::array<uint8_t, 16> &gpio_map,
                               TwoWire *i2c_bus_handler_ptr,
                               uint8_t i2c_sda,
                               uint8_t i2c_scl)
{
    if (m_i2c_bus_handler_ptr)
        return;

    uint8_t addr = 0;
    if (!device_id)
        addr = ADDR_DEVICE_0;
    else
        addr = ADDR_DEVICE_1;

    m_gpio_map = gpio_map;
    m_i2c_bus_handler_ptr = i2c_bus_handler_ptr;

    // 初始化引脚
    for (size_t i = 0; i < IO_MAX_COUNT; i++)
    {
        pinMode(m_gpio_map[i], INPUT);
    }

    // 去初始化 I2C 总线
    i2c_bus_handler_ptr->end();

    // 设置 I2C 引脚
    if (i2c_sda != 0xFF)
        i2c_bus_handler_ptr->setSDA(i2c_sda);
    if (i2c_scl != 0xFF)
        i2c_bus_handler_ptr->setSCL(i2c_scl);

    // 初始化 I2C 总线
    i2c_bus_handler_ptr->begin(addr);

    i2c_bus_handler_ptr->setClock(BAUD);

    i2c_bus_handler_ptr->onReceive([this](int numBytes)
                                   { this->ReceiveCallback(numBytes); });
    i2c_bus_handler_ptr->onRequest([this]()
                                   { this->RequestCallback(); });
}

void SimulationPCAL6416A::Deinit()
{
    if (!m_i2c_bus_handler_ptr)
        return;
    // 去初始化 I2C 总线
    m_i2c_bus_handler_ptr->end();
    m_i2c_bus_handler_ptr = nullptr;
}

void SimulationPCAL6416A::WriteAllGPIO()
{
    for (size_t i = 0; i < IO_MAX_COUNT; i++)
    {
        const uint8_t gpio_idx = i;
        const uint8_t mcu_gpio_code = m_gpio_map[gpio_idx];
        const uint8_t port = gpio_idx / 8;
        const uint8_t bit_index = gpio_idx % 8;

        const bool dir = !!(m_cache_dir_cfg[port] & (1 << bit_index));
        const bool invert = !!(m_cache_polarity[port] & (1 << bit_index));
        if (dir)
        {
            const bool pull_enable = !!(m_cache_pull_enable[port] & (1 << bit_index));
            if (pull_enable)
            {
                const bool pull_select = !!(m_cache_pull_select[port] & (1 << bit_index));
                if (pull_select)
                    pinMode(mcu_gpio_code, INPUT_PULLUP);
                else
                    pinMode(mcu_gpio_code, INPUT_PULLDOWN);
            }
            else
            {
                pinMode(mcu_gpio_code, INPUT);
            }
        }
        else
        {
            pinMode(mcu_gpio_code, OUTPUT);
            const bool gpio_level = !!(m_cache_output[port] & (1 << bit_index));
            const auto mcu_gpio_level = (invert ? !gpio_level : gpio_level) ? HIGH : LOW;
            digitalWrite(mcu_gpio_code, mcu_gpio_level);
        }
    }
}

void SimulationPCAL6416A::ReadAllGPIOLevel()
{
    for (size_t i = 0; i < IO_MAX_COUNT; i++)
    {
        const uint8_t gpio_idx = i;
        const uint8_t mcu_gpio_code = m_gpio_map[gpio_idx];
        const uint8_t port = gpio_idx / 8;
        const uint8_t bit_index = gpio_idx % 8;

        const bool invert = !!(m_cache_polarity[port] & (1 << bit_index));
        bool gpio_level = digitalRead(mcu_gpio_code) == HIGH;
        gpio_level = invert ? !gpio_level : gpio_level;
        m_cache_input[port] =
            gpio_level ? (m_cache_input[port] | (1 << bit_index))
                       : (m_cache_input[port] & ~(1 << bit_index));
    }
}

void SimulationPCAL6416A::ReceiveCallback(int numBytes)
{
    if (!m_i2c_bus_handler_ptr)
        return;
    if (numBytes > 2)
        return;

    for (int i = 0; i < numBytes; i++)
    {
        m_rx_buffer[i] = m_i2c_bus_handler_ptr->read();
    }

    if (numBytes == 2)
    {
        const uint8_t command = m_rx_buffer[0];
        const uint8_t flags = m_rx_buffer[1];
        const Register reg = (Register)command;
        switch (reg)
        {
        case REG_DIRECTION_CONFIG_PORT0:
            m_cache_dir_cfg[0] = flags;
            break;
        case REG_DIRECTION_CONFIG_PORT1:
            m_cache_dir_cfg[1] = flags;
            break;
        case REG_POLARITY_PORT0:
            m_cache_polarity[0] = flags;
            break;
        case REG_POLARITY_PORT1:
            m_cache_polarity[1] = flags;
            break;
        case REG_OUTPUT_PORT0:
            m_cache_output[0] = flags;
            break;
        case REG_OUTPUT_PORT1:
            m_cache_output[1] = flags;
            break;
        case REG_PULL_ENABLE_PORT0:
            m_cache_pull_enable[0] = flags;
            break;
        case REG_PULL_ENABLE_PORT1:
            m_cache_pull_enable[1] = flags;
            break;
        case REG_PULL_SELECT_PORT0:
            m_cache_pull_select[0] = flags;
            break;
        case REG_PULL_SELECT_PORT1:
            m_cache_pull_select[1] = flags;
            break;

        default:
            break;
        }
        WriteAllGPIO();
    }
}

void SimulationPCAL6416A::RequestCallback()
{
    if (!m_i2c_bus_handler_ptr)
        return;

    const uint8_t command = m_rx_buffer[0];
    const Register reg = (Register)command;
    switch (reg)
    {
    case REG_INPUT_PORT0:
        ReadAllGPIOLevel();
        m_i2c_bus_handler_ptr->write(m_cache_input[0]);
        m_i2c_bus_handler_ptr->write(m_cache_input[1]);
        break;
    case REG_INPUT_PORT1:
        ReadAllGPIOLevel();
        m_i2c_bus_handler_ptr->write(m_cache_input[1]);
        break;
    case REG_DIRECTION_CONFIG_PORT0:
        m_i2c_bus_handler_ptr->write(m_cache_dir_cfg[0]);
        m_i2c_bus_handler_ptr->write(m_cache_dir_cfg[1]);
        break;
    case REG_DIRECTION_CONFIG_PORT1:
        m_i2c_bus_handler_ptr->write(m_cache_dir_cfg[1]);
        break;
    case REG_POLARITY_PORT0:
        m_i2c_bus_handler_ptr->write(m_cache_polarity[0]);
        m_i2c_bus_handler_ptr->write(m_cache_polarity[1]);
        break;
    case REG_POLARITY_PORT1:
        m_i2c_bus_handler_ptr->write(m_cache_polarity[1]);
        break;
    case REG_OUTPUT_PORT0:
        m_i2c_bus_handler_ptr->write(m_cache_output[0]);
        m_i2c_bus_handler_ptr->write(m_cache_output[1]);
        break;
    case REG_OUTPUT_PORT1:
        m_i2c_bus_handler_ptr->write(m_cache_output[1]);
        break;
    case REG_PULL_ENABLE_PORT0:
        m_i2c_bus_handler_ptr->write(m_cache_pull_enable[0]);
        m_i2c_bus_handler_ptr->write(m_cache_pull_enable[1]);
        break;
    case REG_PULL_ENABLE_PORT1:
        m_i2c_bus_handler_ptr->write(m_cache_pull_enable[1]);
        break;
    case REG_PULL_SELECT_PORT0:
        m_i2c_bus_handler_ptr->write(m_cache_pull_select[0]);
        m_i2c_bus_handler_ptr->write(m_cache_pull_select[1]);
        break;
    case REG_PULL_SELECT_PORT1:
        m_i2c_bus_handler_ptr->write(m_cache_pull_select[1]);
        break;

    default:
        m_i2c_bus_handler_ptr->write(0xEE);
        m_i2c_bus_handler_ptr->write(0xEE);
        break;
    }
}
